﻿using System;
using System.Runtime.Remoting.Messaging;
using DDDNorthWind.Core.Bus;
using IMessage = DDDNorthWind.Core.Message.IMessage;

namespace DDDNorthWind.Bus
{
    public class FluentBus<TMessage> :
        IFluentBusWithOutMessage<TMessage>
        , IFluentBus<TMessage>
        , IFluentBusAsync<TMessage>
        where TMessage : class, IMessage
    {
        private TMessage mensaje;
        private Action<TMessage> alFinalizar;
        private Action onTimeOutAction;
        private TimeSpan? timeSpan;
        private readonly IBus bus;
        internal FluentBus(IBus bus)
        {
            this.bus = bus;
        }

        public IFluentBus<TMessage> SetMensaje(TMessage toSend)
        {
            mensaje = toSend;
            return this;
        }

        #region IFluentBus<TMessage, out TReply>


        public IFluentBusAsync<TMessage> AddCallback(Action<TMessage> callback)
        {
            alFinalizar = callback;
            return this;
        }



        public IFluentBusAsync<TMessage> AsAsync()
        {
            return this;
        }

        public IFluentBus<TMessage> SetTimeOut(int milisegundos, Action onTimeOut = null)
        {
            return AplicarTimeOut(milisegundos, onTimeOut);
        }



        IFluentBusAsync<TMessage> IFluentBusAsync<TMessage>.SetTimeOut(int milisegundos, Action onTimeOut)
        {
            return AplicarTimeOut(milisegundos, onTimeOut);
        }

        void IFluentBus<TMessage>.Send()
        {

            Action<TMessage> sendAction = bus.Send;

            if (timeSpan.HasValue)
            {
                var asyncResult = sendAction.BeginInvoke(mensaje, null, mensaje);
                RealizarTareaEnUnTiempo(asyncResult, timeSpan.Value, onTimeOutAction);
            }
            else
            {
                sendAction.Invoke(mensaje);
            }

        }

        FluentBus<TMessage> AplicarTimeOut(int milisegundos, Action onTimeOut = null)
        {
            timeSpan = new TimeSpan(0, 0, 0, 0, milisegundos);
            onTimeOutAction = onTimeOut;
            return this;
        }


        #endregion IFluentBus<TMessage, out TReply>

        public Action<IAsyncResult> GetCallBack(Action<TMessage> action)
        {
            Action<IAsyncResult> callback = result =>
                                                {
                                                    var asyncResult = result as AsyncResult;
                                                    if (asyncResult != null)
                                                    {
                                                        var send = asyncResult.AsyncDelegate as Action<TMessage>;

                                                        if (send != null)
                                                        {
                                                            send.EndInvoke(result);
                                                            var msg = asyncResult.AsyncState as TMessage;

                                                            action(msg);
                                                        }
                                                    }
                                                };

            return callback;

        }


        void IFluentBusAsync<TMessage>.Send()
        {

            Action<TMessage> sendAction = bus.Send;

            AsyncCallback asyncCallback = null;

            if (alFinalizar != null)
            {
                asyncCallback = new AsyncCallback(GetCallBack(alFinalizar));
            }

            var asyncResult = sendAction.BeginInvoke(mensaje, asyncCallback, mensaje);

            if (timeSpan.HasValue)
            {
                RealizarTareaEnUnTiempo(asyncResult, timeSpan.Value, onTimeOutAction);
            }
        }

        private void RealizarTareaEnUnTiempo(IAsyncResult asyncResult, TimeSpan timeout, Action onTimeOut)
        {
            if (!asyncResult.AsyncWaitHandle.WaitOne(timeout))
            {
                if (onTimeOut != null)
                    onTimeOut();
                else
                    throw new TimeoutException();
            }
        }

    }
}